/*
	The MIT License

	Copyright (c) 2010 IFMO/GameDev Studio

	Permission is hereby granted, free of charge, to any person obtaining a copy
	of this software and associated documentation files (the "Software"), to deal
	in the Software without restriction, including without limitation the rights
	to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
	copies of the Software, and to permit persons to whom the Software is
	furnished to do so, subject to the following conditions:

	The above copyright notice and this permission notice shall be included in
	all copies or substantial portions of the Software.

	THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
	IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
	FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
	AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
	LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
	OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
	THE SOFTWARE.
*/

#include "d3d_local.h"


/*-----------------------------------------------------------------------------
	Scene shader system :
-----------------------------------------------------------------------------*/

//
//	ERSShadingSystem::ERSShadingSystem
//
ERSShadingSystem::ERSShadingSystem( ERScene *scene )
{
	LOG_INIT("Scene shader manager");

	lua_State *L = CoreLua();
	LuaDefineCFunction(L, "DefineShader", DefineShader, this);
	
	effect	=	NULL;

	ReloadShaders();
}


//
//	ERSShadingSystem::~ERSShadingSystem
//
ERSShadingSystem::~ERSShadingSystem( void )
{
	SAFE_RELEASE( effect );
	
	scene_shaders.clear();
}


//
//	ERSShadingSystem::ReloadShaders
//
void ERSShadingSystem::ReloadShaders( void )
{
	//	execute script :
	scene_shaders.clear();
	CoreExecuteFile("shaders/shaders.lua");
	
	//
	//	compile fx :
	//
	SAFE_RELEASE( effect );

	effect = scene->rs->CompileEffect("shaders/surface.fx");
	
	if (!effect) {
		return;
	}

	//	get parameters :
	p_matrix_view		=	effect->GetParameterByName( NULL, "matrix_view");
	p_matrix_world		=	effect->GetParameterByName( NULL, "matrix_world");
	p_matrix_proj		=	effect->GetParameterByName( NULL, "matrix_proj");
	p_matrix_wvp		=	effect->GetParameterByName( NULL, "matrix_wvp");
	p_matrix_world_tbn	=	effect->GetParameterByName( NULL, "matrix_world_tbn");
	p_view_position		=	effect->GetParameterByName( NULL, "view_position");
															   
	p_is_solid			=	effect->GetParameterByName( NULL, "is_solid");
	p_is_emissive		=	effect->GetParameterByName( NULL, "is_emissive");
	p_is_reflective		=	effect->GetParameterByName( NULL, "is_reflective");
	p_is_distortive		=	effect->GetParameterByName( NULL, "is_distortive");
	p_is_translucent	=	effect->GetParameterByName( NULL, "is_translucent");
	p_is_pixkill		=	effect->GetParameterByName( NULL, "is_pixkill");
	
	p_detail_type		=	effect->GetParameterByName( NULL, "detail_type");
	p_detail_scale		=	effect->GetParameterByName( NULL, "detail_scale");
	p_reflect_fresnel	=	effect->GetParameterByName( NULL, "reflect_fresnel");
	p_emission_scale	=	effect->GetParameterByName( NULL, "emission_scale");
															   
	p_tex_diffuse		=	effect->GetParameterByName( NULL, "sm_diffuse");
	p_tex_specular		=	effect->GetParameterByName( NULL, "sm_specular");
	p_tex_normals		=	effect->GetParameterByName( NULL, "sm_normals");
	p_tex_emission		=	effect->GetParameterByName( NULL, "sm_emission");
	p_tex_detail		=	effect->GetParameterByName( NULL, "sm_detail");
	p_tex_environment	=	effect->GetParameterByName( NULL, "sm_environment");

	p_is_skinned			=	effect->GetParameterByName( NULL, "is_skinned");
	p_joint_matrix			=	effect->GetParameterByName( NULL, "joint_matrix");
	
	//	get techniques :	
	techniques[ RS_TECH_DEFERRED	 ]	=	effect->GetTechniqueByName("tech_deferred");
	techniques[ RS_TECH_EMISSION	 ]	=	effect->GetTechniqueByName("tech_emission");
	techniques[ RS_TECH_DISTORTION	 ]	=	effect->GetTechniqueByName("tech_distortion");
	techniques[ RS_TECH_TRANSLUCENT	 ]	=	effect->GetTechniqueByName("tech_translucent");
	techniques[ RS_TECH_SHADOWMAP	 ]	=	effect->GetTechniqueByName("tech_shadowmap");
	techniques[ RS_TECH_WIREFRAME	 ]	=	effect->GetTechniqueByName("tech_wireframe");
	techniques[ RS_TECH_DEBUG		 ]	=	effect->GetTechniqueByName("tech_debug");
}


//
//	ERSShadingSystem::DefineShader
//
int ERSShadingSystem::DefineShader( lua_State *L )
{
	ERSShadingSystem	*self = (ERSShadingSystem*) LuaGetSelf(L) ;

	try {
		
		IPxRSShader	shader	=	new ERSShader(self->scene, L);
		self->scene_shaders.push_back( shader );
		
	} catch (exception &e) {
		LOG_WARNING("%s", e.what());
	}
	
	return 0;
}


//
//	ERSShadingSystem::GetShader
//
IPxRSShader ERSShadingSystem::GetShader( const EString name )
{
	for (uint i=0; i<scene_shaders.size(); i++) {	
		if (scene_shaders[i]->Name()==name) {
			return scene_shaders[i];
		}
	}
	
	//	shader does not exist, call Lua script to create it :
	CoreExecuteString( va( "DefineShader { name = '%s'; }", name.CStr()));

	LOG_WARNING("scene shader '%s' does not exist, default created", name.CStr());

	//	try to find shader again :
	return GetShader(name);
}


/*-----------------------------------------------------------------------------
	Setup stuff :
-----------------------------------------------------------------------------*/

//
//	ERSShadingSystem::BeginShading
//
bool ERSShadingSystem::BeginShading( ERSShaderTech_t tech )
{
	uint n;

	if (!effect) {
		return false;
	}
	
	if ( !techniques[ tech ] ) {
		return false;
	}
	
	current_tech	=	tech;
	
	HRCALL( effect->SetTechnique( techniques[ tech ] ) );
	HRCALL( effect->Begin( &n, 0 ) );
	HRCALL( effect->BeginPass( 0 ) );
	
	return true;
}


//
//	ERSShadingSystem::SetupShading
//
bool ERSShadingSystem::SetupShading( ERSShader *shader )
{
	if (!effect) return false;
	
	if (!shader->IsTechRequired( current_tech )) {
		return false;
	}
	
	ERSShaderCore_t	*core = shader->GetCore();
	
	scene->rs->txm->SetTexture( 0, core->tex_diffuse		);
	scene->rs->txm->SetTexture( 1, core->tex_specular		);
	scene->rs->txm->SetTexture( 2, core->tex_normals		);
	scene->rs->txm->SetTexture( 3, core->tex_emission		);
	scene->rs->txm->SetTexture( 4, core->tex_detail			);
	scene->rs->txm->SetTexture( 5, core->tex_environment	);
	scene->rs->txm->SetTexture( 6, core->tex_reflect_mask	);
	
	effect->SetInt		(p_detail_type,		core->detail_type);
	effect->SetFloat	(p_detail_scale,	core->detail_scale);
	effect->SetFloat	(p_reflect_fresnel,	core->reflect_fresnel);
	effect->SetFloat	(p_emission_scale,	core->emission_scale);
	effect->SetBool		(p_is_pixkill,		core->is_pixkill);
	
	effect->CommitChanges();
	
	return true;
}


//
//	ERSShadingSystem::EndShading
//
void ERSShadingSystem::EndShading( void )
{
	if (!effect) return;
	
	HRCALL( effect->EndPass() );
	HRCALL( effect->End() );
}


/*-----------------------------------------------------------------------------
	Parameter setup :
-----------------------------------------------------------------------------*/

void ERSShadingSystem::SetWorldMatrix( const EMatrix4 &w )
{
	if (!effect) return;
	matrix_world	=	w;
	matrix_wvp		=	matrix_world * matrix_view * matrix_proj;
	
	D3DXMATRIX	world_tbn(w.Ptr());
	world_tbn(3,0) = 0;
	world_tbn(3,1) = 0;
	world_tbn(3,2) = 0;

	effect->SetMatrix( p_matrix_world,		&D3DXMATRIX(matrix_world.Ptr()) );
	effect->SetMatrix( p_matrix_wvp,		&D3DXMATRIX(matrix_wvp.Ptr()) );
	effect->SetMatrix( p_matrix_world_tbn,	&world_tbn );
}


void ERSShadingSystem::SetViewMatrix( const EMatrix4 &v, EVec4 view_pos )
{
	if (!effect) return;
	matrix_view		=	v;
	matrix_wvp		=	matrix_world * matrix_view * matrix_proj;
	
	effect->SetMatrix( p_matrix_view,	&D3DXMATRIX(matrix_view.Ptr()) );
	effect->SetMatrix( p_matrix_wvp,	&D3DXMATRIX(matrix_wvp.Ptr()) );
	effect->SetVector( p_view_position,	&D3DXVECTOR4(view_pos.Ptr()) );
}


void ERSShadingSystem::SetProjMatrix( const EMatrix4 &p )
{
	if (!effect) return;
	matrix_proj		=	p;
	matrix_wvp		=	matrix_world * matrix_view * matrix_proj;

	effect->SetMatrix( p_matrix_proj,	&D3DXMATRIX(matrix_proj.Ptr()) );
	effect->SetMatrix( p_matrix_wvp,	&D3DXMATRIX(matrix_wvp.Ptr()) );
}


void ERSShadingSystem::SetSkinningEnabled( bool enable )
{
	if (!effect) return;
	effect->SetBool( p_is_skinned, enable );
}

void ERSShadingSystem::SetJoints( IPxTriMesh mesh )
{
	if (mesh->GetJointNum() > RS_MAX_HW_JOINTS)
	{
		LOG_WARNING("number of mesh joint exceeded %d", RS_MAX_HW_JOINTS);
		return;
	}

	if (!effect) return;

	uint joints_num = mesh->GetJointNum();

	vector<D3DXMATRIX> matrices(joints_num);

	for (uint i = 0; i < joints_num; i++)
	{
		EJoint *joint = mesh->GetJoint(i);
		EMatrix4	j_matrix;
		EMatrix4	t1	=	Matrix4Translate( -joint->bind_pos );
		EMatrix4	t2	=	QuatToMatrix( QuatConjugate( joint->bind_orient ) );
		EMatrix4	t3	=	QuatToMatrix( joint->orient );
		EMatrix4	t4	=	Matrix4Translate( joint->position );
		
		j_matrix	=	t1 * t2 * t3 * t4;

		matrices[i] = D3DXMATRIX(j_matrix.Ptr());
	}

	effect->SetMatrixArray( p_joint_matrix,		 &matrices.front(), joints_num);
}

/*-----------------------------------------------------------------------------
	Scene materials :
-----------------------------------------------------------------------------*/

EString LuaGetOptionalFieldString ( lua_State *L, int table_idx, const char *key, const char *def )
{
	if (!lua_istable(L, table_idx)) return def;
	lua_getfield(L, table_idx, key);
	if (!lua_isstring(L, -1)) return def;
	EString r = lua_tostring(L, -1);
	lua_pop(L, 1);
	return r;
}

bool LuaGetOptionalFieldBool ( lua_State *L, int table_idx, const char *key, bool def )
{
	if (!lua_istable(L, table_idx)) return def;
	lua_getfield(L, table_idx, key);
	if (!lua_isboolean(L, -1)) return def;
	bool r = lua_toboolean(L, -1);
	lua_pop(L, 1);
	return r;
}

double LuaGetOptionalFieldNumber ( lua_State *L, int table_idx, const char *key, double def )
{
	if (!lua_istable(L, table_idx)) return def;
	lua_getfield(L, table_idx, key);
	if (!lua_isnumber(L, -1)) return def;
	double r = lua_tonumber(L, -1);
	lua_pop(L, 1);
	return r;
}


//
//	ERSShader::ERSShader
//
ERSShader::ERSShader( ERScene *scene, lua_State *L )
{
	this->scene			=	scene;
	is_textures_loaded	=	false;

	name	=	LuaGetOptionalFieldString	(L, 1,	"name"		,	""	);
	
	if (name=="") {
		RAISE_EXCEPTION("name field must be specified");
	}

	path_diffuse			=	LuaGetOptionalFieldString	(L, 1,	"tex_diffuse"		,	"*grey"	);
	path_specular			=	LuaGetOptionalFieldString	(L, 1,	"tex_specular"		,	"*black");
	path_normals			=	LuaGetOptionalFieldString	(L, 1,	"tex_normals"		,	"*flat"	);
	path_emission			=	LuaGetOptionalFieldString	(L, 1,	"tex_emission"		,	"*black");
	path_detail				=	LuaGetOptionalFieldString	(L, 1,	"tex_detail"		,	"*black");
	path_environment		=	LuaGetOptionalFieldString	(L, 1,	"tex_environment"	,	"*black");
	path_reflect_mask		=	LuaGetOptionalFieldString	(L, 1,	"tex_reflect_mask"	,	"*white");
	
	core.is_solid			=	LuaGetOptionalFieldBool		(L, 1,	"is_solid"			,	true	);
	core.is_emissive		=	LuaGetOptionalFieldBool		(L, 1,	"is_emissive"		,	false	);
	core.is_reflective		=	LuaGetOptionalFieldBool		(L, 1,	"is_reflective"		,	false	);
	core.is_distortive		=	LuaGetOptionalFieldBool		(L, 1,	"is_distortive"		,	false	);
	core.is_translucent		=	LuaGetOptionalFieldBool		(L, 1,	"is_translucent"	,	false	);
	core.is_pixkill			=	LuaGetOptionalFieldBool		(L, 1,	"is_pixkill"		,	false	);
	
	core.detail_type		=	LuaGetOptionalFieldNumber	(L, 1,	"detail_type"		,	0	);
	core.detail_scale		=	LuaGetOptionalFieldNumber	(L, 1,	"detail_scale"		,	4	);
	core.reflect_fresnel	=	LuaGetOptionalFieldNumber	(L, 1,	"reflect_fresnel"	,	1	);
	core.emission_scale		=	LuaGetOptionalFieldNumber	(L, 1,	"emission_scale"	,	1	);
	
	//	fill up pass table :
	required_techs[ RS_TECH_DEFERRED		] = core.is_solid;
	required_techs[ RS_TECH_EMISSION		] = core.is_emissive | core.is_reflective;
	required_techs[ RS_TECH_DISTORTION		] = core.is_distortive;
	required_techs[ RS_TECH_TRANSLUCENT		] = core.is_translucent;
	required_techs[ RS_TECH_SHADOWMAP		] = core.is_solid;
	required_techs[ RS_TECH_WIREFRAME		] = true;
}	


//
//	ERSShader::~ERSShader
//
ERSShader::~ERSShader( void )
{

}



//
//	ERSShader::Setup
//
bool ERSShader::Setup( void )
{
	if (!is_textures_loaded) {
		core.tex_diffuse		=	scene->rs->txm->RegisterTexture( path_diffuse		);
		core.tex_specular		=	scene->rs->txm->RegisterTexture( path_specular		);
		core.tex_normals		=	scene->rs->txm->RegisterTexture( path_normals		);	
		core.tex_emission		=	scene->rs->txm->RegisterTexture( path_emission		);	
		core.tex_detail			=	scene->rs->txm->RegisterTexture( path_detail		);		
		core.tex_environment	=	scene->rs->txm->RegisterTexture( path_environment	);	
		core.tex_reflect_mask	=	scene->rs->txm->RegisterTexture( path_reflect_mask	);	
	}
	
	return scene->sshs->SetupShading(this);
}
